#include "bsdf_mtl.h"
#include "brdf_phong.h"
#include "scene.h"
#include "material.h"
namespace gemren
{
	void bsdf_mtl::announce_properties(const std::string& prefix, property_announcer announcer)
	{
		if(refract && btdf)
			announcer((prefix+".refraction").c_str(), ANNOUNCED_STRING, display_refr_ind.to_string().c_str());
		announcer((prefix+".illum").c_str(), ANNOUNCED_STRING, val_to_str(illum).c_str());
		
		if(brdf) brdf->announce_properties(prefix, announcer);
		if(btdf) btdf->announce_properties(prefix, announcer);

	}
	err_t bsdf_mtl::set_my_float(std::string& name, const float value)
	{
		if(brdf) return brdf->set_float(name, value);
		else return ERR_BAD_PROP;
	}
	err_t bsdf_mtl::set_my_string(std::string& name, const std::string& value)
	{
		err_t myres = ERR_OK;
		if(name=="refraction" || name == "Ni") 
		{
			refr_ind.copy_from_spectrum(display_refr_ind = spectrum_dynamic(value, 0.0f, spectrum::infinity));
			return ERR_OK_RESET_CAUSTICS;
		}
		else if(name == "illum")
		{
			illum = 1;
			std::stringstream ss(value);
			ss>>illum;
			delete brdf; delete btdf; 
			switch(illum)
			{
				/* see http://local.wasp.uwa.edu.au/~pbourke/dataformats/mtl/

				 0		Color on and Ambient off
				 1		Color on and Ambient on
				 2		Highlight on
				 3		Reflection on and Ray trace on
				 4		Transparency: Glass on
 						Reflection: Ray trace on
				 5		Reflection: Fresnel on and Ray trace on
				 6		Transparency: Refraction on
 						Reflection: Fresnel off and Ray trace on
				 7		Transparency: Refraction on
 						Reflection: Fresnel on and Ray trace on
				 8		Reflection on and Ray trace off
				 9		Transparency: Glass on
 						Reflection: Ray trace off
				 10		Casts shadows onto invisible surfaces
				 */
			
			case 2: brdf = new brdf_phong();
					btdf = 0;
					break;
			case 5:
			case 3:
			case 8: brdf = new brdf_specular();
					btdf = 0;

			case 4: brdf = new brdf_specular();
					btdf = new btdf_clear();
					refract = false;
					break;

			case 6:
			case 7: brdf = new brdf_specular();
					btdf = new btdf_clear();
					refract = true;
					break;
			case 9: brdf = 0;
					btdf = new btdf_clear();
					refract = true;
					break;
			case 0:
			case 1:
			default:
				brdf = new brdf_diffuse();
				btdf = 0;
			}
			return ERR_OK_RESET_CAUSTICS;
			
		}
		else myres = ERR_BAD_PROP;
		err_t res = ERR_BAD_PROP;
		if(brdf) res = brdf->set_string(name, value);
		if(btdf && btdf->set_string(name, value) == ERR_OK) return ERR_OK; 
		else return res==ERR_OK? ERR_OK : myres;
	}
	void bsdf_mtl::bounce(random* ran, const scene* sc, intersection* i) const
	{
		if(!btdf) //not transparent
		{
			if(!brdf) i->terminate();
			else brdf->bounce(ran, sc, i);
			return;
		}
		if(!refract)
		{
			if(i->from_inside || ran->getf() <0.8f)
				btdf->bounce(ran, sc,i);
			else 
				brdf->bounce(ran, sc,i);
			return;
		}
		float wl = i->get_wavelength(); //wavelength to compute fresnel with
		embree::Col3f factor; //compensation for filtering colors
		if(/*!refr_ind.is_const() &&*/ wl<0)
		{
			wl = ran->getf();		
			embree::Col3f raycolor = sc->ray_color(wl);
			i->set_wavelength(wl);
		}
		float n = wl>=0?refr_ind[wl]:refr_ind[0]; //wl<0 means refr_ind is const
		
		coord norm(i->normal); 
		const coord &in(i->incoming);

		float cos_incoming = -embree::dot(i->normal, i->incoming);
		if(cos_incoming<0) //coming from inside
		{
			n = embree::rcp(n);
			norm = -norm;
			cos_incoming = -cos_incoming;
		}
		float rcpn = embree::rcp(n);

		coord surfacedir = i->incoming + cos_incoming*norm; //ray direction projected onto the surface plane
		surfacedir = rcpn*surfacedir;
		float sqrsine = embree::dot(surfacedir, surfacedir);
		
		float_type cos_refracted;
		
		float reflected = 1.01f;
		if(sqrsine<1) //nonzero chance of refr
		{//total reflection
			cos_refracted = sqrt( 1.0f-sqrsine); //cosine of refracted ray direction
			
			//compute fresnel coeff
			reflected =  0.5f*( //no polarisation
			embree::sqr((n*cos_refracted - cos_incoming)/ -(cos_incoming + n*cos_refracted))
			+
			embree::sqr((n*cos_incoming - cos_refracted)/ -(n*cos_incoming+ cos_refracted))
			);
			
		}
		if(brdf && ran->getf()<=reflected)
		{//go reflected
			brdf->bounce(ran, sc, i);
		}
		else 
		{//go refracted
			if(reflected>=1.0f) //came here despite total reflection 
			{
				i->dim_light(0.0f);
				return;
			}
			i->incoming  = (surfacedir-cos_refracted*norm); //is unit
			btdf->bounce(ran, sc, i);
		}
	
	}
	void bsdf_mtl::bounce_fast(random* ran, const scene* sc, intersection* i) const
	{
		if(!btdf) //not transparent
		{
			if(!brdf) i->terminate();
			else brdf->bounce_fast(ran, sc, i);
			return;
		}
		//assert btdf!=0 -> brdf != 0
		if(!refract)
		{
			btdf->bounce_fast(ran, sc,i);
			return;
		}
		float n = refr_ind[0.5];
		
		coord norm(i->normal); 
		const coord &in(i->incoming);

		float cos_incoming = -embree::dot(i->normal, i->incoming);
		if(cos_incoming<0) //coming from inside
		{
			n = embree::rcp(n);
			norm = -norm;
			cos_incoming = -cos_incoming;
		}
		float rcpn = embree::rcp(n);

		coord surfacedir = i->incoming + cos_incoming*norm; //ray direction projected onto the surface plane
		surfacedir = rcpn*surfacedir;
		float sqrsine = embree::dot(surfacedir, surfacedir);
		
		float_type cos_refracted;
		
		float reflected = 1.01f;
		if(sqrsine<1) //nonzero chance of refr
		{//total reflection
			cos_refracted = sqrt( 1.0f-sqrsine); //cosine of refracted ray direction
			
			//compute fresnel coeff
			reflected =  0.5f*( //no polarisation
			embree::sqr((n*cos_refracted - cos_incoming)/ -(cos_incoming + n*cos_refracted))
			+
			embree::sqr((n*cos_incoming - cos_refracted)/ -(n*cos_incoming+ cos_refracted))
			);
			
		}
		if(brdf && ran->getf()<=reflected)
		{//go reflected
			brdf->bounce_fast(ran, sc, i);
		}
		else 
		{//go refracted
			if(reflected>=1.0f) //came here despite total reflection 
			{
				i->dim_light(0.0f);
				return;
			}
			i->incoming  = (surfacedir-cos_refracted*norm); //is unit
			btdf->bounce_fast(ran, sc, i);
		}
	
	}
	
}